#coding=utf8
# Copyright (c) 2016 Tinydot. inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

'''
Cumulative utility
'''

import time


class GradRecordor(object):
    '''Save data with Graduation format'''

    def __init__(self,):
        self.records = {}

    def reinit(self,):
        try:
            self.records = {}
        except Exception as e:
            raise e
        finally:
            pass

    def _add_data(self, dtype, key, gap, data):
        try:
            return self.__add_data(dtype, key, gap, data)
        except Exception as e:
            raise e

        finally:
            pass

    def __add_data(self, dtype, key, gap, data):
        '''Count data And Graudation related data'''
        if key is None or key == "":
            return False

        if gap <=0 and dtype == "gdata":
            return False

        if key not in self.records:
            self.records[key] = {  "type"      :dtype,
                                   "total"     :0.0,
                                   "count"     :0.0,
                                   "max"       :data,
                                   "min"       :data,
                                   "startime"  :time.time(),
                                   "last_atime":time.time(),
                                  }
            if dtype == "gdata":
                self.records[key]["grade"] = {}
                self.records[key]["gap"]   = gap

        if dtype == "gdata":
            if gap != self.records[key]["gap"]:
                return False

            index = int(data/gap)
            if index not in self.records[key]["grade"]:
                self.records[key]["grade"][index]    = 1
            else:
                self.records[key]["grade"][index]   += 1

        self.records[key]["max"] = max(self.records[key]["max"], data)
        self.records[key]["min"] = min(self.records[key]["min"], data)

        self.records[key]["total"]     += data
        self.records[key]["count"]     += 1
        self.records[key]["last_atime"] = time.time()
        return True

    def gpercent(self, key, gap, value):
        if key in self.records:
            index = int(value/gap)
            if "grade" in self.records[key]:
                if index in self.records[key]["grade"]:
                    return self.records[key]["grade"][index]/float(self.records[key]["count"])
        return None

    def add_cdata(self, key, data):
        '''add count data'''
        return self._add_data("cdata", key, 0, data)

    def add_gdata(self, key, gap, data):
        '''Graudation related data'''
        return self._add_data("gdata", key, gap, data)


    def __add__(self, other):
        '''voerwrite pluse operator'''
        new_instance         = type(self)()
        new_instance.records = self.records

        # add
        for d in other.records:
            if d in new_instance.records:
                if new_instance.records[d]["type"] != other.records[d]["type"]:
                    continue

                #gdata
                if "gap" in new_instance.records[d]:
                    if "gap" not in other.records[d]:
                        return self

                    gap = new_instance.records[d]["gap"]
                    if gap != other.records[d]["gap"]:
                        return self

                    for k in other.records[d]["grade"]:
                        if k in new_instance.records[d]["grade"]:
                            new_instance.records[d]["grade"][k] += other.records[d]["grade"][k]
                        else:
                            new_instance.records[d]["grade"][k]  = other.records[d]["grade"][k]

                new_instance.records[d]["total"] += other.records[d]["total"]
                new_instance.records[d]["count"] += other.records[d]["count"]
                new_instance.records[d]["min"]   =  min(new_instance.records[d]["min"], other.records[d]["min"])
                new_instance.records[d]["max"]   =  max(new_instance.records[d]["max"], other.records[d]["max"])
                new_instance.records[d]["startime"]     =  min(new_instance.records[d]["startime"], other.records[d]["startime"])
                new_instance.records[d]["last_atime"]   =  max(new_instance.records[d]["last_atime"], other.records[d]["last_atime"])

            else:
                new_instance.records[d] = other.records[d]

            # check
            # del cdf_updatime, cdf keys
            if "cdf" in new_instance.records[d]:
                del new_instance.records[d]["cdf"]

            if "cdf_updatime" in new_instance.records[d]:
                del new_instance.records[d]["cdf_updatime"]

        # return data
        return new_instance


    def get(self, key, subkey, refault_r=None):
        if key in self.records:
            if subkey in self.records[key]:
                return self.records[key][subkey]
        return refault_r

    def get_keys(self, k_filter=[]):
        returns = []
        for k in self.records:
            if k in k_filter:
                continue
            returns.append(k)
        return returns

    def get_average(self, key):
        if key in self.records:
            return self.records[key]["total"]/float(self.records[key]["count"])
        return None

    def get_count(self, key):
        if key in self.records:
            return self.records[key]["count"]
        return 0

    def get_avg_speed(self, key):
        if key in self.records:
            return self.records[key]["total"]/(self.records[key]["last_atime"] - self.records[key]["startime"])
        return None

    def get_max(self, key):
        if key in self.records:
            return self.records[key]["max"]
        return None

    def get_min(self, key):
        if key in self.records:
            return self.records[key]["min"]
        return None

    def get_qps(self, key):
        '''Get qurey per seconds'''

        if key in self.records:
            t_cost = self.records[key]["last_atime"] - \
                     self.records[key]["startime"]

            if t_cost > 0.0:
                return self.records[key]["count"]/t_cost

        return None


class CDF(GradRecordor):
    '''Cumulative Distribution Function (CDF)
       Important Note: functions added in this class should not be frequently called
    '''

    def __calc_cdf(self, key):
        '''Calculate Cumulative Distribution'''
        if key not in self.records:
            return None

        if self.records[key]["type"] != "gdata":
            return None

        if "cdf_updatime" in self.records[key]:
            if self.records[key]["cdf_updatime"] > self.records[key]["last_atime"]:
                return True

        self.records[key]["cdf"]  = []
        min_data = min(0, self.records[key]["min"])
        max_data = self.records[key]["max"]
        gap_data = self.records[key]["gap"]

        index_start = int(min_data/gap_data)
        index_end   = int(max_data/gap_data)

        pre_sum = 0
        while index_start <= index_end:
            if index_start in self.records[key]["grade"]:
                d_sum = self.records[key]["grade"][index_start]
            else:
                d_sum = 0

            index_sum = d_sum + pre_sum
            self.records[key]["cdf"].append(index_sum/float(self.records[key]["count"]))

            pre_sum   = index_sum
            index_start += 1

        self.records[key]["cdf_updatime"] = time.time()
        return True

    def __add__(self, other):
        return super(CDF, self).__add__(other)

    def get_tail(self, key, percent):
        '''eg: get 99.99% tail, get_tail(key, 0.9999)'''
        if self.__calc_cdf(key) != True:
            return None
        cdf = self.records[key]["cdf"]

        index = -1

        for i in range(len(cdf)):
            if cdf[i] > percent:
                break
            else:
                index = i

        # calc data
        if index < 0:
            return None

        return index*self.records[key]["gap"] + min(0, self.records[key]["min"])

    def get_mean(self, key):
        return self.get_tail(key, 0.5)

    def get_cdf(self, key):
        if self.__calc_cdf(key) != True:
            return None
        return self.records[key]["cdf"]

    def __str__(self,):
        ''' '''
        return "(CDF)"